package com.sojson.util.linux.impl;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;

import com.sojson.util.StringUtil;
import com.sojson.util.file.FileUtil;
import com.sojson.util.linux.LinuxBean;
import com.sojson.util.linux.LinuxUtilInterface;

import ch.ethz.ssh2.ChannelCondition;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.SCPClient;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;

/**
 * 连接Linux服务器SSH2工具类
 * 
 * @author liu
 * @date 2020-07-30
 */
public class Ssh2UtilImpl implements LinuxUtilInterface {

    private static Ssh2UtilImpl sSH2UtilsImpl;

    private Ssh2UtilImpl() {}

    @Override
    public LinuxBean executiveCommandGetStatusCode(String comm, long timeOut) throws IOException {
        LinuxBean linuxBean = null;

        Connection conn = null;
        Session sess = null;
        try {
            conn = getConn();
            // 打开一个会话,只有通过身份认证才能工作,并发访问没有数量限制
            sess = conn.openSession();
            // 执行linux命令
            sess.execCommand(comm);
            // 暂停线程一段时间,linux可能要执行很久
            // 设置读取超时时间(毫秒),超时就结束,提前读完也马上结束,如果不设置会读取一下就结束,导致只读取了一小部分数据
            // 也会导致后面获取服务器返回的状态吗的时候获取不到
            // 必须在获取状态码和获取数据之前使用,获取数据必须在new StreamGobbler()之后使用
            sess.waitForCondition(ChannelCondition.EXIT_STATUS, timeOut);
            // sessOne.getExitStatus()成功为1,超时为null,其他为失败
            linuxBean = new LinuxBean(sess.getExitStatus());
        } finally {
            close(conn, sess);
        }
        return linuxBean;
    }

    @Override
    public LinuxBean executiveCommandGetStatusCode(LinuxBean conn, String comm, long timeOut) throws IOException {
        LinuxBean linuxBean = null;

        Session sess = null;
        try {
            Connection connection = conn.getConn();
            // 打开一个会话,只有通过身份认证才能工作,并发访问没有数量限制
            sess = connection.openSession();
            // 执行linux命令
            sess.execCommand(comm);
            // 暂停线程一段时间,linux可能要执行很久
            // 设置读取超时时间(毫秒),超时就结束,提前读完也马上结束,如果不设置会读取一下就结束,导致只读取了一小部分数据
            // 也会导致后面获取服务器返回的状态吗的时候获取不到
            // 必须在获取状态码和获取数据之前使用,获取数据必须在new StreamGobbler()之后使用
            sess.waitForCondition(ChannelCondition.EXIT_STATUS, timeOut);
            // sessOne.getExitStatus()成功为1,超时为null,其他为失败
            linuxBean = new LinuxBean(sess.getExitStatus());
        } finally {
            close(null, sess);
        }
        return linuxBean;
    }

    @Override
    public LinuxBean executiveCommandGetStringList(String comm, long timeOut) throws IOException {
        LinuxBean linuxBean = null;
        Integer exitStatus = null;
        ArrayList<String> string = new ArrayList<>();

        Connection conn = null;
        Session sess = null;
        InputStream stdout = null;
        InputStream streamStdout = null;
        InputStreamReader streamReaderStdout = null;
        BufferedReader brStdout = null;
        try {
            conn = getConn();
            // 打开一个会话,只有通过身份认证才能工作,并发访问没有数量限制
            sess = conn.openSession();
            // 执行linux命令
            sess.execCommand(comm);
            // 获取返回输入流
            stdout = sess.getStdout();
            // 普通输入流暂停线程的时候也会暂停,StreamGobbler暂停线程的时候还会继续读取
            streamStdout = new StreamGobbler(stdout);
            // 暂停线程一段时间,linux可能要执行很久
            // 设置读取超时时间(毫秒),超时就结束,提前读完也马上结束,如果不设置会读取一下就结束,导致只读取了一小部分数据
            // 也会导致后面获取服务器返回的状态吗的时候获取不到
            // 必须在获取状态码和获取数据之前使用,获取数据必须在new StreamGobbler()之后使用
            sess.waitForCondition(ChannelCondition.EXIT_STATUS, timeOut);
            // sessOne.getExitStatus()成功为1,超时为null,其他为失败
            exitStatus = sess.getExitStatus();
            if (streamStdout != null) {
                streamReaderStdout = new InputStreamReader(streamStdout);
                brStdout = new BufferedReader(streamReaderStdout);
                while (true) {
                    String line = brStdout.readLine();
                    if (StringUtil.isBlankObject(line)) {
                        break;
                    }
                    string.add(line);
                }
            }
            linuxBean = new LinuxBean(exitStatus, string);
        } finally {
            close(conn, sess);
            FileUtil.closeStream(brStdout, streamReaderStdout);
            FileUtil.closeStream(streamStdout, stdout);
        }
        return linuxBean;
    }

    @Override
    public LinuxBean executiveCommandGetStringList(LinuxBean conn, String comm, long timeOut) throws IOException {
        LinuxBean linuxBean = null;
        Integer exitStatus = null;
        ArrayList<String> string = new ArrayList<>();

        Session sess = null;
        InputStream stdout = null;
        InputStream streamStdout = null;
        InputStreamReader streamReaderStdout = null;
        BufferedReader brStdout = null;
        try {
            Connection connection = conn.getConn();
            // 打开一个会话,只有通过身份认证才能工作,并发访问没有数量限制
            sess = connection.openSession();
            // 执行linux命令
            sess.execCommand(comm);
            // 获取返回输入流
            stdout = sess.getStdout();
            // 普通输入流暂停线程的时候也会暂停,StreamGobbler暂停线程的时候还会继续读取
            streamStdout = new StreamGobbler(stdout);
            // 暂停线程一段时间,linux可能要执行很久
            // 设置读取超时时间(毫秒),超时就结束,提前读完也马上结束,如果不设置会读取一下就结束,导致只读取了一小部分数据
            // 也会导致后面获取服务器返回的状态吗的时候获取不到
            // 必须在获取状态码和获取数据之前使用,获取数据必须在new StreamGobbler()之后使用
            sess.waitForCondition(ChannelCondition.EXIT_STATUS, timeOut);
            // sessOne.getExitStatus()成功为1,超时为null,其他为失败
            exitStatus = sess.getExitStatus();
            if (streamStdout != null) {
                streamReaderStdout = new InputStreamReader(streamStdout);
                brStdout = new BufferedReader(streamReaderStdout);
                while (true) {
                    String line = brStdout.readLine();
                    if (StringUtil.isBlankObject(line)) {
                        break;
                    }
                    string.add(line);
                }
            }
            linuxBean = new LinuxBean(exitStatus, string);
        } finally {
            close(null, sess);
            FileUtil.closeStream(brStdout, streamReaderStdout);
            FileUtil.closeStream(streamStdout, stdout);
        }
        return linuxBean;
    }

    @Override
    public LinuxBean executiveCommandGetInputStream(String comm, long timeOut) throws IOException {
        LinuxBean linuxBean = null;

        Connection conn = null;
        Session sess = null;
        InputStream stdout = null;
        try {
            conn = getConn();
            // 打开一个会话,只有通过身份认证才能工作,并发访问没有数量限制
            sess = conn.openSession();
            // 执行linux命令
            sess.execCommand(comm);
            // 获取返回输入流
            stdout = sess.getStdout();
            // 普通输入流暂停线程的时候也会暂停,StreamGobbler暂停线程的时候还会继续读取
            InputStream streamStdout = new StreamGobbler(stdout);
            // 暂停线程一段时间,linux可能要执行很久
            // 设置读取超时时间(毫秒),超时就结束,提前读完也马上结束,如果不设置会读取一下就结束,导致只读取了一小部分数据
            // 也会导致后面获取服务器返回的状态吗的时候获取不到
            // 必须在获取状态码和获取数据之前使用,获取数据必须在new StreamGobbler()之后使用
            sess.waitForCondition(ChannelCondition.EXIT_STATUS, timeOut);
            // sessOne.getExitStatus()成功为1,超时为null,其他为失败
            linuxBean = new LinuxBean(sess.getExitStatus(), streamStdout);
        } finally {
            close(conn, sess);
            FileUtil.closeStream(stdout);
        }
        return linuxBean;
    }

    @Override
    public LinuxBean executiveCommandGetInputStream(LinuxBean conn, String comm, long timeOut) throws IOException {
        LinuxBean linuxBean = null;

        Session sess = null;
        InputStream stdout = null;
        try {
            Connection connection = conn.getConn();
            // 打开一个会话,只有通过身份认证才能工作,并发访问没有数量限制
            sess = connection.openSession();
            // 执行linux命令
            sess.execCommand(comm);
            // 获取返回输入流
            stdout = sess.getStdout();
            // 普通输入流暂停线程的时候也会暂停,StreamGobbler暂停线程的时候还会继续读取
            InputStream streamStdout = new StreamGobbler(stdout);
            // 暂停线程一段时间,linux可能要执行很久
            // 设置读取超时时间(毫秒),超时就结束,提前读完也马上结束,如果不设置会读取一下就结束,导致只读取了一小部分数据
            // 也会导致后面获取服务器返回的状态吗的时候获取不到
            // 必须在获取状态码和获取数据之前使用,获取数据必须在new StreamGobbler()之后使用
            sess.waitForCondition(ChannelCondition.EXIT_STATUS, timeOut);
            // sessOne.getExitStatus()成功为1,超时为null,其他为失败
            linuxBean = new LinuxBean(sess.getExitStatus(), streamStdout);
        } finally {
            close(null, sess);
            FileUtil.closeStream(stdout);
        }
        return linuxBean;
    }

    @Override
    public void upload(String filePath, String targetPath) throws IOException {
        Connection conn = null;
        try {
            conn = getConn();
            // 创建SCPClient对象
            SCPClient createScpClient = conn.createSCPClient();
            // 文件传输
            createScpClient.put(filePath, targetPath);
        } finally {
            close(conn, null);
        }
    }

    @Override
    public void upload(LinuxBean conn, String filePath, String targetPath) throws IOException {
        Connection connection = conn.getConn();
        // 创建SCPClient对象
        SCPClient createScpClient = connection.createSCPClient();
        // 文件传输
        createScpClient.put(filePath, targetPath);
    }

    @Override
    public void upload(byte[] bytes, String filePath, String fileName) throws IOException {
        Connection conn = null;
        try {
            conn = getConn();
            // 创建SCPClient对象
            SCPClient createScpClient = conn.createSCPClient();
            // 文件传输
            createScpClient.put(bytes, fileName, filePath);
        } finally {
            close(conn, null);
        }
    }

    @Override
    public void upload(LinuxBean conn, byte[] bytes, String filePath, String fileName) throws IOException {
        Connection connection = conn.getConn();
        // 创建SCPClient对象
        SCPClient createScpClient = connection.createSCPClient();
        // 文件传输
        createScpClient.put(bytes, fileName, filePath);
    }

    @Override
    public LinuxBean remove(String fileName, long timeOut) throws IOException {
        return executiveCommandGetStatusCode("rm -rf " + fileName, timeOut);
    }

    @Override
    public LinuxBean remove(LinuxBean conn, String fileName, long timeOut) throws IOException {
        return executiveCommandGetStatusCode(conn, "rm -rf " + fileName, timeOut);
    }

    @Override
    public void download(String filePath, String targetPath) throws IOException {
        Connection conn = null;
        try {
            conn = getConn();
            // 创建SCPClient对象
            SCPClient createScpClient = conn.createSCPClient();
            // 文件传输
            createScpClient.get(filePath, targetPath);
        } finally {
            close(conn, null);
        }
    }

    @Override
    public void download(LinuxBean conn, String filePath, String targetPath) throws IOException {
        Connection connection = conn.getConn();
        // 创建SCPClient对象
        SCPClient createScpClient = connection.createSCPClient();
        // 文件传输
        createScpClient.put(filePath, targetPath);
    }

    @Override
    public OutputStream download(String filePath) throws IOException {
        Connection conn = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            conn = getConn();
            byteArrayOutputStream = new ByteArrayOutputStream();
            // 创建SCPClient对象
            SCPClient createScpClient = conn.createSCPClient();
            // 文件传输
            createScpClient.get(filePath, byteArrayOutputStream);
        } finally {
            close(conn, null);
        }
        return byteArrayOutputStream;
    }

    @Override
    public OutputStream download(LinuxBean conn, String filePath) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = null;
        Connection connection = conn.getConn();
        byteArrayOutputStream = new ByteArrayOutputStream();
        // 创建SCPClient对象
        SCPClient createScpClient = connection.createSCPClient();
        // 文件传输
        createScpClient.get(filePath, byteArrayOutputStream);
        return byteArrayOutputStream;
    }

    public Connection getConn() throws IOException {
        return getConn(IP, PORT, USERNAME, PASSWORD);
    }

    public Connection getConn(String ip, String username, String password) throws IOException {
        return getConn(ip, PORT, username, password);
    }

    public Connection getConn(String ip, int port, String username, String password) throws IOException {
        Connection conn = null;
        // 创建linux对象
        conn = new Connection(ip, port);
        auth(conn, username, password);
        return conn;
    }

    @Override
    public LinuxBean getConnection() throws IOException {
        return new LinuxBean(getConn());
    }

    @Override
    public LinuxBean getConnection(String ip, String password) throws IOException {
        return new LinuxBean(getConn(ip, PORT, USERNAME, password));
    }

    @Override
    public LinuxBean getConnection(String ip, String username, String password) throws IOException {
        return new LinuxBean(getConn(ip, PORT, username, password));
    }

    @Override
    public LinuxBean getConnection(String ip, int port, String password) throws IOException {
        return new LinuxBean(getConn(ip, port, USERNAME, password));
    }

    @Override
    public LinuxBean getConnection(String ip, int port, String username, String password) throws IOException {
        return new LinuxBean(getConn(ip, port, username, password));
    }

    /**
     * 认证方法
     * 
     * @param conn
     * @param password
     * @throws IOException 
     */
    private void auth(Connection conn, String username, String password) throws IOException {
        // 连接linux
        conn.connect();
        // 认证,返回成功或失败(true:成功,false:失败)
        conn.authenticateWithPassword(username, password);
    }

    public void closeOne(Connection conn) throws IOException {
        if (StringUtil.isNotBlankObject(conn)) {
            conn.close();
        }
    }

    public void close(Connection conn, Session sess) throws IOException {
        if (StringUtil.isNotBlankObject(conn)) {
            conn.close();
        }
        if (StringUtil.isNotBlankObject(sess)) {
            sess.close();
        }
    }

    public void closeList(Connection conn, ArrayList<Session> sesss) throws IOException {
        if (StringUtil.isNotBlankObject(sesss)) {
            for (Session sess : sesss) {
                if (StringUtil.isNotBlankObject(sess)) {
                    sess.close();
                }
            }
        }
        if (StringUtil.isNotBlankObject(conn)) {
            conn.close();
        }
    }

    @Override
    public void utilCloseOne(LinuxBean conn) throws IOException {
        closeOne(conn.getConn());
    }

    @Override
    public void utilClose(LinuxBean conn) throws IOException {
        close(conn.getConn(), conn.getSess());
    }

    @SuppressWarnings("unchecked")
    @Override
    public void utilCloseList(LinuxBean conn) throws IOException {
        closeList((Connection)conn.getConnsList().get(0), (ArrayList<Session>)conn.getConnsList().get(1));
    }

    public static Ssh2UtilImpl getInstances() {
        if (StringUtil.isBlankObject(sSH2UtilsImpl)) {
            sSH2UtilsImpl = new Ssh2UtilImpl();
        }
        return sSH2UtilsImpl;
    }

}